#ifndef symbolstable_h__
#define symbolstable_h__

#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <iostream>
#include <list>


namespace CCompiler
{
	/* spawned hierarchy */
	class SymType;
	class SymFunction;
	class SymTypeStruct;
    class SynStatementBlock;
    class ParserWithSymTable;

	enum SymbolType
	{
		stVariable,
		stType,
		stFunction,
        stFunctionType,
		stAlias,
		stInt,
		stFloat,
		stVoid,
		stChar,
		stStruct,
		stPointer,
        stFunctionPointer,
		stArray,
		stEnum
	};

	class SymTable;
	class SymFunctionInterface;
	class SymTypeStruct;

	class Symbol
	{
		friend class SymTable;
		std::string name_;
	public:
		Symbol(const std::string name): name_(name) {}
		std::string getName() { return name_; }
        void rename(const std::string &name) { name_ = name; }
		virtual void print(std::ostream &stream, int ident);
		virtual bool isType() { return false; }
		virtual SymbolType getSymbolType() = 0;
		virtual SymType *getType() = 0;
	};

	class SymType: public Symbol
	{
	public:
		SymType(const std::string &name): Symbol(name) {}
		virtual SymbolType getSymbolType() { return stType; }
		virtual bool isType() { return true; };
		virtual SymType *getType() { return this; }
	};

	class SymVariable: public Symbol
	{
		SymType *type_;
	public:
		SymVariable(SymType *type, const std::string name): Symbol(name), type_(type) {}
		virtual void print(std::ostream &stream, int ident);
		virtual SymbolType getSymbolType() { return stVariable; }
		virtual SymType *getType() { return type_->getType(); }
	};

	class SymTypeInt: public SymType
	{
	public:
		SymTypeInt(): SymType("int") {}
		virtual SymbolType getSymbolType() { return stInt; }
	};

	class SymTypeFloat: public SymType
	{
	public:
		SymTypeFloat(): SymType("float") {}
		virtual SymbolType getSymbolType() { return stFloat; }
	};

	class SymTypeVoid: public SymType
	{
	public:
		SymTypeVoid(): SymType("void") {}
		virtual SymbolType getSymbolType() { return stVoid; }
	};

	class SymTypeChar: public SymType
	{
	public:
		SymTypeChar(): SymType("char") {}
		virtual SymbolType getSymbolType() { return stChar; }
	};

	class SymTypePointer: public SymType
	{
		SymType *pointed_;
	public:
		SymTypePointer(SymType *pointed, const std::string &name): SymType(name), pointed_(pointed) {}
		virtual SymbolType getSymbolType() { return stPointer; }
        SymType *getPointed() { return pointed_; }
	};

    class SymTableInterface
    {
        public:
            virtual SymTable &getClosestTable() = 0;
            virtual bool exists(const std::string &name) = 0;
            virtual Symbol *find(const std::string &name) = 0;
            virtual void insert(Symbol *smbl) = 0;
    };

    class SymTable: public SymTableInterface
	{
		typedef std::map<std::string, Symbol *> InternalTable;
		InternalTable tbl_;
		unsigned int anon_type_counter_;
		friend class SymFunctionInterface;
		friend class SymTypeStruct;
	public:
		SymTable(): anon_type_counter_(0) {}
		virtual void insert(Symbol *smbl)
		{
			tbl_.insert(std::make_pair(smbl->name_, smbl)).first;
		}
		virtual Symbol *find(const std::string &name)
		{
			return tbl_[name];
		}
		virtual bool exists(const std::string &name)
		{
			return tbl_.count(name) != 0;
		}
        virtual SymTable &getClosestTable()
        {
            return *this;
        }
		void print(std::ostream &stream, int ident = 0)
		{
			for(InternalTable::iterator it = tbl_.begin(); it != tbl_.end(); ++it)
			{
				it->second->print(stream, ident);
				stream << '\n';
			}
		}
		std::string genAnonName();
		~SymTable()
		{
			for(InternalTable::iterator it = tbl_.begin(); it != tbl_.end(); ++it)
				delete it->second;
		}
	};

    class SymTableStack:public SymTableInterface
    {
        typedef std::vector<SymTableInterface *> InnerStackPresentation;
        typedef InnerStackPresentation::reverse_iterator InnerIterator;
        InnerStackPresentation tables_;
    public:
        SymTableStack(SymTableInterface *global): tables_(1, global) {}
        void push(SymTableInterface *elm)
        {
            tables_.push_back(elm);
        }
        virtual SymTable &getClosestTable()
        {
            return tables_.back()->getClosestTable();
        }
        virtual bool exists(const std::string &name)
        {
            return find(name) ? true: false;
        }
        virtual Symbol *find(const std::string &name)
        {
            for(InnerIterator it = tables_.rbegin(); it != tables_.rend(); ++it)
            {
                if((*it)->exists(name)) return (*it)->find(name);
            }
            return NULL;
        }
        virtual void insert(Symbol *smbl)
        {
            tables_.back()->insert(smbl);
        }
    };

	class SymAlias: public SymType
	{
		SymType *type_;
	public:
		SymAlias(SymType *type, const std::string &name): SymType(name), type_(type) {}
		virtual void print(std::ostream &stream, int ident);
		virtual SymbolType getSymbolType() { return stAlias; }
		virtual SymType *getType() { return type_->getType(); }
	};

    class SymFunctionType: public SymType
    {
    public:
        typedef std::vector<SymType *> ArgsVector;
    protected:
        SymType *result_;
        std::vector<SymType *> args_;
    public:
        SymFunctionType(SymType *result, const std::string &name): SymType(name), result_(result) {}
        void push_arg(SymType *arg) { args_.push_back(arg); }
        SymType *getResultType() { return result_; }
        size_t getArgsCount() { return args_.size(); }
        std::string genFuntionTypeName(const std::string &name)
        {
            std::string ftype_name = result_->getName() + name + '(';
            if(args_.size())
            {
                for(size_t i = 0; i < args_.size() - 1; ++i)
                {
                    ftype_name += args_[i]->getName();
                    ftype_name += ',';
                }
                ftype_name += args_.back()->getName();

            }
            ftype_name += ')';
            return ftype_name;
        }
        ArgsVector::iterator argsBegin()
        {
            return args_.begin();
        }
        ArgsVector::iterator argsEnd()
        {
            return args_.end();
        }
        friend bool operator==(const SymFunctionType &op1, const SymFunctionType &op2)
        {
            return op1.result_ == op2.result_ && std::equal(op1.args_.begin(), op1.args_.end(), op2.args_.begin());
        }
        friend bool operator!=(const SymFunctionType &op1, const SymFunctionType &op2)
        {
            return !(op1 == op2);
        }
        virtual SymbolType getSymbolType() { return stFunctionType; }
    };

	class SymFunction: public Symbol, public SymTableInterface
	{
		SymFunctionType *ftype_;
        SymTable args_;
        SynStatementBlock *body_;
	public:
		typedef SymFunctionType::ArgsVector ArgsVector;
        SymFunction(SymFunctionType *ftype, const std::string &name):
          Symbol(name), ftype_(ftype), body_(NULL) {}

        ArgsVector::iterator argsBegin()
        {
            return ftype_->argsBegin();
        }
        ArgsVector::iterator argsEnd()
        {
            return ftype_->argsEnd();
        }
        virtual size_t argsCount() { return ftype_->getArgsCount(); }
        virtual bool exists(const std::string &name)
        {
            return args_.exists(name);
        }
        virtual Symbol *find(const std::string &name)
        {
            return args_.find(name);
        }
        virtual void insert(Symbol *smbl)
        {
            args_.insert(smbl);
        }
        virtual SymTable &getClosestTable() { return args_; }
        void assignBody(SynStatementBlock *body) { body_ = body; }
		bool isProto() { return body_ == NULL; }
		virtual SymbolType getSymbolType() { return stFunction; }
		virtual SymType *getType() { return ftype_; }
        SymType *getResultType() { return ftype_->getResultType(); }
        virtual void print(std::ostream &stream, int ident);
        std::string genAnonName() { return args_.genAnonName(); }

	};

    class SymFunctionPointer: public SymTypePointer
    {
    public:
        SymFunctionPointer(SymFunctionType *ftype, const std::string &name): SymTypePointer(ftype, name) {}
        virtual SymbolType getSymbolType() { return stFunctionPointer; }
        virtual SymType *getType() { return this; }
    };

	class SymTypeArray: public SymTypePointer
	{
		size_t size_;
	public:
		SymTypeArray(SymType *type, size_t size, const std::string &name):
		  SymTypePointer(type, name), size_(size) {}
		virtual SymbolType getSymbolType() { return stArray; }
		virtual void print(std::ostream &stream, int ident);
	};

	//class SymTypeEnum: public SymType
	//{
	//public:
	//	virtual SymbolType getSymbolType() { return stEnum; }
	//};

	class SymTypeStruct: public SymType
	{
        friend class ParserWithSymTable;
		SymTable tbl_;
		bool proto;
	public:
		SymTypeStruct(const std::string &name): SymType(name), proto(true) {}
		void gotImplementation() { proto = false; }
		bool isProto() { return proto; }
		void putField(SymVariable *field) { tbl_.insert(field); }
		bool fieldExists(const std::string &name) { return tbl_.exists(name); }
        Symbol *getField(const std::string &name) { return tbl_.find(name); }
		virtual SymbolType getSymbolType() { return stStruct; }
		virtual void print(std::ostream &stream, int ident);
	};
}
#endif // symbolstable_h__
